// ======================================================================
// Hell Lua Bus ( Lua Bind) Lib
// 
// Copyright 2013 Hell-Prototypes
//
// http://code.google.com/p/hell-prototypes/
//
// This is free software, licensed under the terms of the GNU General
// Public License as published by the Free Software Foundation.
// ======================================================================

#include <usb.h> /* libusb header */
//#include <unistd.h> /* for geteuid */
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#include "define.h"

//==============================================================================
#define IAP_DEBUG			0

#define IAP_BL_START_ADDR	0x760
#define IAP_PGM_MIN_LEN		0x12

#define IAP_PAGE_MAX_LEN        32

#define IAP_CMD_NOP		0x00
#define IAP_CMD_PAGE_ERASE	0xA1
#define IAP_CMD_PAGE_FILL	0xA2
#define IAP_CMD_PAGE_WR		0xA3
#define IAP_CMD_APP_ENTRY_WR	0xA4

#define IAP_ACK			0x06
#define IAP_NAK			0x15

#define IAP_APP_ENTRY_RST	0x00

static int g_callback_ref;
#define IAP_LUA_NO_CALLBACK_REF		0

static void l_iap_callback_lua_func(lua_State *L, int progress)
{
	if(( L == NULL) || (g_callback_ref == IAP_LUA_NO_CALLBACK_REF)) {
		return;
	}

	lua_rawgeti(L, LUA_REGISTRYINDEX, g_callback_ref); /* push stored function */

	/* push arg to stack*/
	lua_pushnumber (L, progress);

	/* call function*/
	int ret = lua_pcall(L, 1, 0, LUA_MULTRET);
	if(ret != LUA_OK ) {
		printf("<IAP> Run call back function error, code = %d\r\n", ret);
	}

	//printf("***g_callback_ref = %d, lua_gettop(L) = %d\r\n", g_callback_ref, lua_gettop(L));
}

static int l_iap_open(lua_State *L)
{
	int top = lua_gettop(L);
	const char * p_com_name;
	char com[16];

    if(top != 1) {
        lua_pop(L, lua_gettop(L));//clear stack
        lua_pushnumber (L, -ERR_PARAM_NUM);
        return 1;
    }

    if(!lua_isstring(L, -1)) {
        lua_pop(L, lua_gettop(L));//clear stack
        lua_pushnumber (L, -ERR_PARAM_TYPE);
        return 1;
    }
    p_com_name = lua_tostring (L, -1);
    if(p_com_name == NULL) {
        lua_pop(L, lua_gettop(L));//clear stack
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }
    
    if((strncmp("COM", p_com_name, 3) != 0) 
		|| (strlen(p_com_name) < 4)
		|| (strlen(p_com_name) > 6)) {
		lua_pop(L, lua_gettop(L));//clear stack
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
	}
	
	strcpy(com, p_com_name);

    int ret;
    ret = serial_open(com, 9600, &g_serial_fd);
    //printf("serial_open ret = %d, ifd = %d\r\n", ret, g_serial_fd.ifd);

    lua_pushnumber (L, ret);

    return 1;
}

static int l_iap_send_cmd(char * cmd)
{
    int retry = 3, ret;
    unsigned char rec[5];

    rec[0] = 0x00;
    
#if IAP_DEBUG
    for(retry = 0; retry<5; retry++) {
		printf("%02x ", (unsigned char)cmd[retry]);
	}
	printf("\r\n");
	//return 0;
#endif

    while(retry--) {
        ret = serial_send(&g_serial_fd, (unsigned char *)cmd, 5);
        if(ret < 0) {
            return ret;
        }
        ret = serial_recv(&g_serial_fd, rec, 1);
        if(ret < 0) {
            return ret;
        }
        if(rec[0] == IAP_ACK) {
            return 0;
        }
#if IAP_DEBUG
		printf("********* NAK: retry %d\r\n",retry);
#endif
    }
    return -1;
}

static int l_iap_sync()
{
    int count = 8, ret;
    unsigned char rec[5];
    unsigned char cmd[5];

#if IAP_DEBUG
	return 0;
#endif
    long timeout_tmp = serial_recv_timeout;

    serial_recv_timeout = 50;

    cmd[0] = IAP_CMD_NOP;
    rec[0] = 0x00;
    while(count--) {
        ret = serial_send(&g_serial_fd, cmd, 1);
        if(ret < 0) {
            serial_recv_timeout = timeout_tmp;
            return ret;
        }
        serial_recv(&g_serial_fd, rec, 1);

        if(rec[0] == IAP_NAK) {
            //printf("*** l_iap_sync done\r\n", ret);
            serial_recv_timeout = timeout_tmp;
            return 0;
        }
    }

    serial_recv_timeout = timeout_tmp;
    return -1;
}

static int l_iap_wr_app_entry(char entry)
{
    char cmd[5];
    cmd[0] = IAP_CMD_APP_ENTRY_WR;
    cmd[1] = entry;
    cmd[2] = 0;
    cmd[3] = 0;
    cmd[4] = 0;

    return l_iap_send_cmd(cmd);
}

static int l_iap_erase_page(int page_base)
{
    char cmd[5];
    cmd[0] = IAP_CMD_PAGE_ERASE;
    cmd[1] = page_base & 0xff;
    cmd[2] = (page_base>>8) & 0xff;
    cmd[3] = 0;
    cmd[4] = 0;

    return l_iap_send_cmd(cmd);
}

static int l_iap_write_page(int page_base)
{
    char cmd[5];
    cmd[0] = IAP_CMD_PAGE_WR;
    cmd[1] = page_base & 0xff;
    cmd[2] = (page_base>>8) & 0xff;
    cmd[3] = 0;
    cmd[4] = 0;

    return l_iap_send_cmd(cmd);
}

static int l_iap_wr_one_page(const int page_base, const char * buf, const int buf_len)
{
    char cmd[5];
    int ret, i;

    cmd[0] = IAP_CMD_PAGE_FILL;

    for(i=0; i<buf_len; i+=2) {
        cmd[1] = (page_base + i) & 0xff;
        cmd[2] = ((page_base + i)>>8) & 0xff;
        cmd[3] = buf[i];
        cmd[4] = buf[i+1];

        ret = l_iap_send_cmd(cmd);
        if(ret < 0) {
            return ret;
        }
    }
#if IAP_DEBUG
	printf("----------------\r\n");
#endif
    ret = l_iap_erase_page(page_base);
    if(ret < 0) {
        return ret;
    }

    ret = l_iap_write_page(page_base);
#if IAP_DEBUG
	printf("================\r\n");
#endif
    return ret;
}

static int l_iap_download(lua_State *L, char * pgm_buf, const int pgm_len)
{
    if((pgm_buf == NULL) || (pgm_len == 0)) {
        return -ERR_PARAM;
    }

    char app_entry = pgm_buf[0];
    int page_base = 0;
    int ret, buf_len;

    //replace reset vector to bootloader entry
    pgm_buf[0] = (char)(IAP_BL_START_ADDR/2-1);
    pgm_buf[1] = (char)((IAP_BL_START_ADDR/2-1)>>8) | 0xC0;//0xCx - RJMP
    //printf("pgm_buf[0] = 0x%02x, pgm_buf[1] = 0x%02x\r\n", (unsigned char)pgm_buf[0],(unsigned char)pgm_buf[1]);

	l_iap_callback_lua_func(L,  -2);
    //Sync rs232 buffer
    ret = l_iap_sync();
    if(ret < 0) {
        return ret;
    }

	l_iap_callback_lua_func(L, -1);
    //Set app entry to reset vector
    ret = l_iap_wr_app_entry(IAP_APP_ENTRY_RST);
    if(ret < 0) {
        return ret;
    }

    for(page_base = 0; page_base < pgm_len; page_base += IAP_PAGE_MAX_LEN) {
        if(pgm_len -  page_base < IAP_PAGE_MAX_LEN) {
            buf_len = pgm_len -  page_base;
        } else {
            buf_len = IAP_PAGE_MAX_LEN;
        }

        l_iap_callback_lua_func(L,  (page_base * 100)/pgm_len);

        ret = l_iap_wr_one_page(page_base, &pgm_buf[page_base], buf_len);

        if(ret < 0) {
            return ret;
        }
    }

    //Set app entry to new app entry
    ret = l_iap_wr_app_entry(app_entry);
    if(ret < 0) {
        return ret;
    }
    
    l_iap_callback_lua_func(L,  100);

    return NO_ERR;
}

static int l_iap_run(lua_State *L)
{
	int table_top = lua_gettop(L);
	
	g_callback_ref = IAP_LUA_NO_CALLBACK_REF;
	
	if(table_top > 2) {
		lua_pushnumber (L, -ERR_PARAM_NUM);
		return 1;
	} else {
		if(table_top == 2) {
			if (lua_type(L,-1) == LUA_TFUNCTION) {
				// setup callback
				g_callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);
			} else {
				lua_pop (L, 1);
                lua_pushnumber (L, -ERR_PARAM_NUM);
                return 1;
			}
		} else {
            lua_pushnumber (L, -ERR_PARAM_NUM);
		    return 1;
        }
	}
	
    if(g_serial_fd.ifd > 0) {
        char pgm_buf[IAP_BL_START_ADDR+1];
        int pgm_len;
	
        pgm_len = get_byte_table_value(L, pgm_buf, sizeof(pgm_buf));

        if((pgm_len > IAP_PGM_MIN_LEN) && (pgm_len <= IAP_BL_START_ADDR)) {
            lua_pushnumber (L, l_iap_download(L, pgm_buf, pgm_len));
        } else {
            lua_pushnumber (L, -ERR_PARAM);
        }
    } else {
        lua_pushnumber (L, -ERR_OPEN);
    }
    
    if(g_callback_ref != IAP_LUA_NO_CALLBACK_REF) {
    	luaL_unref(L, LUA_REGISTRYINDEX, g_callback_ref);
		g_callback_ref = IAP_LUA_NO_CALLBACK_REF;
	} 

    return 1;
}
static int l_iap_close(lua_State *L)
{
    lua_pop(L, lua_gettop(L));//clear stack

    serial_close(&g_serial_fd);
    //printf("serial_open ifd = %d\r\n", g_serial_fd.ifd);

    g_serial_fd.ifd = 0;

    return 0;
}
